package com.mrd.framework.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mrd.framework.utils.HttpUtils;
import com.mrd.gtimp.base.service.ModuleBiz;
import com.mrd.gtimp.base.service.RoleBiz;
import com.mrd.gtimp.base.service.UserBiz;
import com.mrd.spring.filter.WeChatFilter;
import com.mrd.util.endecrypt.MD5Util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 安全配置类 Created by winaya on 2016/6/6.
 */
@Configuration
@EnableWebSecurity
// @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@Order(SecurityProperties.IGNORED_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private static final Logger logger = LoggerFactory
            .getLogger(SecurityConfiguration.class);
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Resource
    private UserBiz userBiz;
    @Resource
    private RoleBiz roleBiz;
    @Resource
    private ModuleBiz moduleBiz;
    @Autowired
    private SecurityProperties security;

    @Autowired
    private MrdSecurityProperties mrdSecurityProperties;
    /**
     * 根据用户名查用户
     */
    private static final String USERS_BY_USERNAME_QUERY = "select t.u_account username,t.u_pwd password,case when t.u_isabled+t.u_state=0 then 1 else 0 end as enabled from T_SYS_USER t where lower(t.u_account) = lower(?)";
    /**
     * 根据用户名查权限
     */
    private static final String AUTHORITIES_BY_USERNAME_QUERY = "select t.u_account username,'ROLE_USER' authority from T_SYS_USER t where lower(t.u_account) = lower(?)";

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication = auth
                .jdbcAuthentication();
        jdbcAuthentication.dataSource(jdbcTemplate.getDataSource())
                .usersByUsernameQuery(USERS_BY_USERNAME_QUERY)
                .authoritiesByUsernameQuery(AUTHORITIES_BY_USERNAME_QUERY)
                .passwordEncoder(new PasswordEncoder() {
                    @Override
                    public String encode(CharSequence rawPassword) {
                        return rawPassword.toString();
                    }

                    @Override
                    public boolean matches(CharSequence rawPassword, String encodedPassword) {
                        return MD5Util.string2MD5(rawPassword.toString()).equals(encodedPassword);
                    }
                });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 过滤不要权限的页面
        http.authorizeRequests().antMatchers(security.getIgnored().toArray(new String[0]))
                .permitAll();
//		http.addFilterAfter(new WeChatInterceptor(), FilterSecurityInterceptor.class);
        http.exceptionHandling().accessDeniedHandler(new DefaultAccessDeniedHandler())
                .authenticationEntryPoint(new DefaultAuthenticationEntryPoint(mrdSecurityProperties
                        .getSessionFailPage()));
        http.csrf().disable();
        http.formLogin().loginPage(mrdSecurityProperties.getLoginPage()).permitAll()
                .loginProcessingUrl(mrdSecurityProperties.getLoginPost())
                .usernameParameter(mrdSecurityProperties.getNameUserName())
                .passwordParameter(mrdSecurityProperties.getNamePasswordName());
        http.logout().logoutSuccessUrl(mrdSecurityProperties.getLoginOutPage())
                .logoutUrl(mrdSecurityProperties.getLoginOutPost()).permitAll()
                .clearAuthentication(true);
        http.formLogin().successHandler(new LoginAuthenticationSuccessHandler())
                .failureHandler(new LoginAuthenticationFailureHandler());
        http.headers().frameOptions().sameOrigin();
        http.sessionManagement().sessionFixation().newSession();
        super.configure(http);
    }

    private static void loginDetails(HttpServletRequest request, HttpServletResponse response,
                                     Authentication authentication) {
        Enumeration<String> headerNames = request.getHeaderNames();
        logger.info("打印header");
        logger.info("========================");
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            logger.info("{}:{}", headerName, request.getHeader(headerName));
        }
        logger.info("========================");
        // 登录者的信息
        Object details = authentication.getDetails();
        logger.info("验证结果:{}", authentication.isAuthenticated());
        logger.info(details.toString());
    }

    /**
     * 登录成功的处理
     */
    private class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

        private final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                            Authentication authentication) throws IOException, ServletException {
            SecurityConfiguration.loginDetails(request, response, authentication);
            if (!authentication.isAuthenticated()) {
                return;
            }
            User user = (User) authentication.getPrincipal();
            HttpUtils.getSession()
                    .setAttribute(SecurityConstants.SESSION_USER_ACCOUNT_NAME, user.getUsername());
            // 根据是否为ajax请求做不同的回应
            if (HttpUtils.isAjax()) {
                response.setHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
                Map<String, Object> rs = new HashMap<>();
                rs.put("rs", true);
                rs.put("msg", "登录成功!");
                ObjectMapper mapper = new ObjectMapper();
                try (PrintWriter pw = response.getWriter()) {
                    pw.println(mapper.writeValueAsString(rs));
                    pw.flush();
                } catch (Exception e) {
                    logger.error("输出登录结果时出错!", e);
                }
            } else {
                response.sendRedirect(HttpUtils
                        .coverUrlUseContext(mrdSecurityProperties.getSuccessPage()));
            }
        }
    }

    /**
     * 校验失败处理方法
     */
    private class LoginAuthenticationFailureHandler implements AuthenticationFailureHandler {

        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                            AuthenticationException exception) throws IOException, ServletException {
            if ((exception instanceof BadCredentialsException)) {
                if (HttpUtils.isAjax()) {
                    response.setStatus(HttpServletResponse.SC_OK);
                    Map<String, Object> rs = new HashMap<>();
                    rs.put("rs", false);
                    rs.put("msg", "用户名或密码错误!");
                    ObjectMapper mapper = new ObjectMapper();
                    try (PrintWriter pw = response.getWriter()) {
                        pw.println(mapper.writeValueAsString(rs));
                        pw.flush();
                    } catch (Exception e) {
                        logger.error("权限校验失败,用户名或密码错误!", e);
                    }
                } else {
                    response.sendRedirect("/login?u=" + request.getParameter("userName"));
                }
            }
        }
    }

    /**
     * 拒绝访问调用
     */
    private class DefaultAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response,
                           AccessDeniedException accessDeniedException) throws IOException, ServletException {
            logger.info("权限校验失败!");
        }
    }

    /**
     * 校验匿名权限失败时的处理类
     */
    private class DefaultAuthenticationEntryPoint implements AuthenticationEntryPoint {

        private String errorPage;

        DefaultAuthenticationEntryPoint(String errorPage) {
            this.errorPage = errorPage;
        }

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                             AuthenticationException authException) throws IOException, ServletException {
            logger.info("匿名用户登录需要权限的模块或者session过期!");
            logger.info("访问地址:" + request.getRequestURI());
            if (HttpUtils.isAjax()) {
                request.setAttribute(WebAttributes.ACCESS_DENIED_403, authException);
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                Map<String, Object> rs = new HashMap<>();
                rs.put("rs", false);
                rs.put("noLogin", true);
                rs.put("msg", "登录超时!");
                ObjectMapper mapper = new ObjectMapper();
                try (PrintWriter pw = response.getWriter()) {
                    pw.println(mapper.writeValueAsString(rs));
                    pw.flush();
                } catch (Exception e) {
                    logger.error("登录超时!", e);
                }
            } else {
                if (errorPage != null) {
                    // Put exception into request scope (perhaps of use to a view)
                    request.setAttribute(WebAttributes.ACCESS_DENIED_403, authException);
                    // Set the 403 status code.
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    // forward to error page.
                    // RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
                    response.sendRedirect(errorPage);
                    // dispatcher.forward(request, response);
                } else {
                    response.sendError(HttpServletResponse.SC_FORBIDDEN, authException
                            .getMessage());
                }
            }
        }
    }

    /**
     * 配置过滤器
     */
    @Bean
    public FilterRegistrationBean weChatFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new WeChatFilter());
        registration.addUrlPatterns("/wx/*");
        registration.setName("weChatFilter");
        return registration;
    }
}
